﻿using System;
using System.Globalization;

namespace CoreRay
{
    //-----------------------------------------------------------------------------------------
    /// <summary>
    /// Structure that represents a ray.
    /// </summary>
    //-----------------------------------------------------------------------------------------

    public class Ray : IEquatable<Ray>
    {
        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Initializes a new instance of the <see cref="Ray"/> struct.
        /// </summary>
        /// <remarks>
        /// The direction vector must be normalized before creating a ray. No check is made by the 
        /// struct for performance reasons.
        /// </remarks>
        /// <param name="origin">The origin vector.</param>
        /// <param name="direction">The direction vector. This vector must be normalized.</param>
        //-----------------------------------------------------------------------------------------

        public Ray(Vector3 origin, Vector3 direction) : this(origin, direction, 0.001f, float.PositiveInfinity)
        {

        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Initializes a new instance of the <see cref="Ray"/> struct.
        /// </summary>
        /// <remarks>
        /// The direction vector must be normalized before creating a ray. No check is made by the 
        /// struct for performance reasons.
        /// </remarks>
        /// <param name="origin">The origin vector.</param>
        /// <param name="direction">The direction vector. This vector must be normalized.</param>
        /// <param name="minimumT">The minimum value allowed for parametric parameter t.</param>
        /// <param name="maximumT">The maximum value allowed for parametric parameter t.</param>
        //-----------------------------------------------------------------------------------------

        public Ray(Vector3 origin, Vector3 direction, float minimumT, float maximumT)
        {
            this.Origin = origin;
            this.Direction = direction;
            this.MaximumT = maximumT;
            this.MinimumT = minimumT;
            this.SlopeInfo = new RaySlope(this);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the origin vector of the ray.
        /// </summary>
        /// <value>The origin vector.</value>
        //-----------------------------------------------------------------------------------------

        public Vector3 Origin
        {
            get;
            private set;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the normalized direction vector of the ray.
        /// </summary>
        /// <value>The direction vector.</value>
        //-----------------------------------------------------------------------------------------

        public Vector3 Direction
        {
            get;
            private set;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the minimum value allowed for parametric parameter t.
        /// </summary>
        /// <value>The minimum value allowed for parametric parameter t.</value>
        //-----------------------------------------------------------------------------------------

        public float MinimumT
        {
            get;
            private set;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the maximum value allowed for parametric parameter t.
        /// </summary>
        /// <value>The maximum value allowed for parametric parameter t.</value>
        //-----------------------------------------------------------------------------------------

        public float MaximumT
        {
            get;
            private set;
        }


        public RaySlope SlopeInfo
        {
            get;
            private set;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Computes a point along the ray at the specified parametric point t.
        /// </summary>
        /// <param name="t">The parametric point t.</param>
        /// <returns>Point a the specified parametric point t.</returns>
        //-----------------------------------------------------------------------------------------

        public Vector3 ComputePoint(float t)
        {
            if(t < this.MinimumT || t > this.MaximumT)
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Value of T({0}) is not in the range [{1}, {2}] for the ray.", t, this.MinimumT, this.MaximumT));
            }

            return this.Origin + this.Direction * t;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <param name="obj">Another object to compare to.</param>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the 
        /// same value; otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public override bool Equals(object obj)
        {
            return obj is Ray ? Equals((Ray) obj) : false;
        }

 
        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; 
        /// otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public bool Equals(Ray other)
        {
            return this.Origin == other.Origin && this.Direction == other.Direction;
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the equal operator.
        /// </summary>
        /// <param name="ray1">The first ray.</param>
        /// <param name="ray2">The second ray.</param>
        /// <returns>
        /// true if the two rays are equal; otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public static bool operator ==(Ray ray1, Ray ray2)
        {
            return ray1.Equals(ray2);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Implements the unequal operator.
        /// </summary>
        /// <param name="ray1">The first ray.</param>
        /// <param name="ray2">The second ray.</param>
        /// <returns>
        /// true if the two rays are unequal; otherwise, false.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public static bool operator !=(Ray ray1, Ray ray2)
        {
            return !ray1.Equals(ray2);
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public override int GetHashCode()
        {
            return this.Origin.GetHashCode() ^ this.Direction.GetHashCode();
        }


        //-----------------------------------------------------------------------------------------
        /// <summary>
        /// Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> containing a description of the ray.
        /// </returns>
        //-----------------------------------------------------------------------------------------

        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "Ray({0}, {1})", this.Origin.ToString(), this.Direction.ToString());
        } 
    }
}
